frontend/pages/e/[uuid]/details.tsx (view raw)
1import moment from 'moment';
2import Linkify from 'linkify-react';
3import Tooltip from '@mui/material/Tooltip';
4import IconButton from '@mui/material/IconButton';
5import Box from '@mui/material/Box';
6import Link from '@mui/material/Link';
7import Card from '@mui/material/Card';
8import Container from '@mui/material/Container';
9import TextField from '@mui/material/TextField';
10import Typography from '@mui/material/Typography';
11import TuneIcon from '@mui/icons-material/Tune';
12import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
13import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
14import {useTheme} from '@mui/material/styles';
15import {DatePicker} from '@mui/x-date-pickers/DatePicker';
16import {PropsWithChildren, useState} from 'react';
17import {useTranslation} from 'next-i18next';
18import pageUtils from '../../../lib/pageUtils';
19import DetailsLink from '../../../containers/DetailsLink';
20import ShareEvent from '../../../containers/ShareEvent';
21import PlaceInput from '../../../containers/PlaceInput';
22import LangSelector from '../../../components/LangSelector';
23import usePermissions from '../../../hooks/usePermissions';
24import useEventStore from '../../../stores/useEventStore';
25import useToastStore from '../../../stores/useToastStore';
26import EventLayout, {TabComponent} from '../../../layouts/Event';
27import {
28 EventByUuidDocument,
29 useUpdateEventMutation,
30} from '../../../generated/graphql';
31import {langLocales} from '../../../locales/constants';
32import {getLocaleForLang} from '../../../lib/getLocale';
33
34interface Props {
35 eventUUID: string;
36 announcement?: string;
37}
38
39const Page = (props: PropsWithChildren<Props>) => {
40 return <EventLayout {...props} Tab={DetailsTab} />;
41};
42
43const DetailsTab: TabComponent<Props> = ({}) => {
44 const {t} = useTranslation();
45 const {
46 userPermissions: {canEditEventDetails},
47 } = usePermissions();
48 const theme = useTheme();
49 const [updateEvent] = useUpdateEventMutation();
50 const addToast = useToastStore(s => s.addToast);
51 const setEventUpdate = useEventStore(s => s.setEventUpdate);
52 const event = useEventStore(s => s.event);
53 const [isEditing, setIsEditing] = useState(false);
54
55 if (!event) return null;
56
57 const hasGeoloc = event.latitude && event.longitude;
58
59 const onSave = async e => {
60 try {
61 const {uuid, ...data} = event;
62 delete data.linkedEvent;
63 delete data.isReturnEvent;
64 const {
65 id,
66 travels,
67 waitingPassengers,
68 __typename,
69 administrators,
70 passengers,
71 ...input
72 } = data;
73 await updateEvent({
74 variables: {
75 uuid,
76 eventUpdate: {
77 ...input,
78 },
79 },
80 refetchQueries: ['eventByUUID'],
81 });
82 setIsEditing(false);
83 } catch (error) {
84 console.error(error);
85 addToast(t('event.errors.cant_update'));
86 }
87 };
88
89 const modifyButton = isEditing ? (
90 <Tooltip
91 title={t('event.details.save')}
92 sx={{
93 position: 'absolute',
94 top: theme.spacing(2),
95 right: theme.spacing(2),
96 }}
97 >
98 <IconButton color="primary" onClick={onSave}>
99 <CheckCircleOutlineIcon />
100 </IconButton>
101 </Tooltip>
102 ) : (
103 <Tooltip
104 title={t('event.details.modify')}
105 sx={{
106 position: 'absolute',
107 top: theme.spacing(2),
108 right: theme.spacing(2),
109 }}
110 >
111 <IconButton color="primary" onClick={() => setIsEditing(true)}>
112 <TuneIcon />
113 </IconButton>
114 </Tooltip>
115 );
116
117 return (
118 <Box
119 sx={{
120 position: 'relative',
121 }}
122 >
123 <Container
124 sx={{
125 p: 4,
126 mt: 6,
127 mb: 11,
128 mx: 0,
129 [theme.breakpoints.down('md')]: {
130 p: 2,
131 mt: 13,
132 },
133 }}
134 >
135 <Card
136 sx={{
137 position: 'relative',
138 maxWidth: '100%',
139 width: '480px',
140 p: 2,
141 }}
142 >
143 <Typography variant="h4" pb={2}>
144 {t('event.details')}
145 </Typography>
146 {canEditEventDetails() && modifyButton}
147 {(isEditing || event.name) && (
148 <Box pt={2} pr={1.5}>
149 <Typography variant="overline">
150 {t('event.fields.name')}
151 </Typography>
152 <Typography>
153 {isEditing ? (
154 <TextField
155 size="small"
156 fullWidth
157 value={event.name}
158 onChange={e => setEventUpdate({name: e.target.value})}
159 name="name"
160 id="EditEventName"
161 />
162 ) : (
163 <Typography id="EventName">{event.name}</Typography>
164 )}
165 </Typography>
166 </Box>
167 )}
168 {(isEditing || event.date) && (
169 <Box pt={2} pr={1.5}>
170 <Typography variant="overline">
171 {t('event.fields.date')}
172 </Typography>
173 {isEditing ? (
174 <Typography>
175 <DatePicker
176 slotProps={{
177 textField: {
178 size: 'small',
179 id: `EditEventDate`,
180 fullWidth: true,
181 placeholder: t('event.fields.date_placeholder'),
182 },
183 }}
184 format="DD/MM/YYYY"
185 value={moment(event.date)}
186 onChange={date =>
187 setEventUpdate({
188 date: !date ? null : moment(date).format('YYYY-MM-DD'),
189 })
190 }
191 />
192 </Typography>
193 ) : (
194 <Box position="relative">
195 <Typography id="EventDate">
196 {moment(event.date).format('DD/MM/YYYY')}
197 </Typography>
198 </Box>
199 )}
200 </Box>
201 )}
202 {(isEditing || event.address) && (
203 <Box pt={2} pr={1.5}>
204 <Typography variant="overline">
205 {t('event.fields.address')}
206 </Typography>
207 {isEditing ? (
208 <PlaceInput
209 place={event.address}
210 latitude={event.latitude}
211 longitude={event.longitude}
212 onSelect={({place, latitude, longitude}) =>
213 setEventUpdate({
214 address: place,
215 latitude,
216 longitude,
217 })
218 }
219 />
220 ) : (
221 <Box position="relative">
222 <Typography
223 id="EventAddress"
224 title={t`placeInput.noCoordinates`}
225 sx={{
226 pr: 3,
227 display: 'inline-flex',
228 alignItems: 'center',
229 columnGap: 1,
230 }}
231 >
232 <Link
233 target="_blank"
234 rel="noreferrer"
235 href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
236 event.address
237 )}`}
238 onClick={e => e.preventDefault}
239 >
240 {event.address}
241 </Link>
242 {!hasGeoloc && (
243 <InfoOutlinedIcon fontSize="small" color="warning" />
244 )}
245 </Typography>
246 </Box>
247 )}
248 </Box>
249 )}
250 {(isEditing || event.description) && (
251 <Box pt={2} pr={1.5}>
252 <Typography variant="overline">
253 {t('event.fields.description')}
254 </Typography>
255 {isEditing ? (
256 <Typography>
257 <TextField
258 fullWidth
259 multiline
260 maxRows={4}
261 inputProps={{maxLength: 250}}
262 value={event.description || ''}
263 onChange={e =>
264 setEventUpdate({description: e.target.value})
265 }
266 id={`EditEventDescription`}
267 name="description"
268 />
269 </Typography>
270 ) : (
271 <Typography
272 id="EventDescription"
273 sx={{pr: 3, whiteSpace: 'pre-line'}}
274 >
275 <Linkify options={{render: DetailsLink}}>
276 {event.description}
277 </Linkify>
278 </Typography>
279 )}
280 </Box>
281 )}
282 {(isEditing || event.lang) && (
283 <Box pt={2} pr={1.5}>
284 <Typography variant="overline">
285 {t('event.fields.lang')}
286 </Typography>
287 {isEditing ? (
288 <LangSelector
289 value={event.lang}
290 onChange={lang => setEventUpdate({lang})}
291 />
292 ) : (
293 <Typography id="EventLang" sx={{pr: 3}}>
294 {langLocales[event.lang]}
295 </Typography>
296 )}
297 </Box>
298 )}
299 {!isEditing && !!event.email && (
300 <Box pt={2} pr={1.5}>
301 <Typography variant="overline">
302 {t('options.plus.creator')}
303 </Typography>
304 <Typography id="EventLang" sx={{pr: 3}}>
305 {event.email}
306 </Typography>
307 </Box>
308 )}
309 {!isEditing && (
310 <ShareEvent
311 title={`Caroster ${event.name}`}
312 sx={{width: '100%', mt: 2}}
313 />
314 )}
315 </Card>
316 </Container>
317 </Box>
318 );
319};
320
321export const getServerSideProps = pageUtils.getServerSideProps(
322 async (context, apolloClient) => {
323 const {uuid} = context.query;
324 const {host = ''} = context.req.headers;
325 let event = null;
326
327 // Fetch event
328 try {
329 const {data} = await apolloClient.query({
330 query: EventByUuidDocument,
331 variables: {uuid},
332 });
333 event = data?.eventByUUID?.data;
334 } catch (error) {
335 return {
336 notFound: true,
337 };
338 }
339
340 const description = await getLocaleForLang(
341 event?.attributes?.lang,
342 'meta.description'
343 );
344
345 return {
346 props: {
347 eventUUID: uuid,
348 metas: {
349 title: event?.attributes?.name || '',
350 description,
351 url: `https://${host}${context.resolvedUrl}`,
352 },
353 },
354 };
355 }
356);
357export default Page;